/*
 * Copyright (C) 2019-2020 Alibaba Group Holding Limited
 */

#include <stdio.h>
#include <stdlib.h>
#include <aos/aos.h>
#include <aos/kernel.h>
#include <ipc.h>
#include <csi_core.h>
#include <drv/codec.h>
#include <aos/list.h>

#include "voice.h"

#define TAG "Vap"

#define MESSAGE_NUM 10

struct __voice {
    voice_pcm_param_t       *mic_param;
    voice_pcm_param_t       *ref_param;
    voice_param_t            param;

    voice_evt_t              cb;
    void                    *priv;
    aos_queue_t              queue;
    aos_task_t               ipc_task;
    uint8_t                  buffer[sizeof(message_t) * MESSAGE_NUM];
    voice_ch_t              *ch;
    voice_ch_io_t           *ops;
    voice_pcm_t             *pcm;
    voice_kwmap_t           *kwmap;
};

static void voice_set_kwmap(voice_t *v, voice_kwmap_t *map)
{
    aos_check_param(v && map);

    if (!v->kwmap) {
        v->kwmap = aos_malloc_check(sizeof(voice_kwmap_t));
    }

    *(v->kwmap) = *map;

    // LOGD(TAG, "receive kw map count: %d", v->kwmap->count);
    // for (int i = 0; i < v->kwmap->count; i++) {
    //     LOGD(TAG, "id %d word: %s", v->kwmap->map[i].id, v->kwmap->map[i].kword);
    // }
}

static void voice_msg_process(void *priv, voice_msg_t *msg)
{
    int cmd = msg->command;
    voice_t *v = (voice_t*)priv;
    int evt_id = 0;

    if (cmd == VOICE_KWS_CMD) {
        evt_id = VOICE_ASR_EVT;
        LOGD(TAG,"\r\nasr event %d\r\n", *(int *)msg->req_data);
    } else if (cmd == VOICE_SILENCE_CMD) {
        evt_id = VOICE_SILENCE_EVT;
        LOGD(TAG,"\r\nvad event\r\n");
    } else if (cmd == VOICE_DATA_CMD) {
        evt_id = VOICE_DATA_EVT;
    } else if (cmd == VOICE_KW_MAP_CMD) {
        voice_set_kwmap(v, msg->req_data);
        return;
    } else {
        return;
    }

    if (v->cb) {
        v->cb(v->priv, evt_id, msg->req_data, msg->req_len);
    }
}

static void voice_data_send(void *priv, void *data, int len)
{
    voice_t *v = (voice_t *)priv;

    voice_msg_send(v, VOICE_PCM_CMD, data, len, 1);
}

static void voice_pcm_param_init(voice_t *v)
{
    voice_pcm_param_t *p = v->mic_param;
    if (p) {
        p->period_bytes = p->channles_sum * p->rate * p->sample_bits / 8 * v->param.cts_ms / 1000;
    }

    p = v->ref_param;
    if (p) {
        p->period_bytes = p->channles_sum * p->rate * p->sample_bits / 8 * v->param.cts_ms / 1000;
    }

    // voice_dcache_writeback((uint32_t *)v, sizeof(voice_t));
    voice_dcache_writeback((uint32_t *)v->mic_param, sizeof(voice_pcm_param_t));
    voice_dcache_writeback((uint32_t *)v->ref_param, sizeof(voice_pcm_param_t));
    voice_dcache_writeback((uint32_t *)v->param.ai_param, v->param.ai_param_len);

    voice_msg_send(v, VOICE_PCM_PARAM_SET_CMD, (void *)v, sizeof(voice_t), 1);
    if (v->param.ipc_mode == 1) {
        v->pcm = pcm_init(voice_data_send, v);
        pcm_mic_config(v->pcm, v->mic_param);
        pcm_ref_config(v->pcm, v->ref_param);
        pcm_start(v->pcm);
    }
}

static void voice_cp_start(voice_t *v)
{
    voice_msg_send(v, VOICE_CP_START_CMD, NULL, 0, 1);
}

static void voice_ch_init(voice_t *v)
{
    if (v->param.ipc_mode) {
        v->ops = ipc_ch_get();
        v->ch = v->ops->init(voice_msg_process, v);
    } else {
        // TODO

    }
}

static void voice_ch_deinit(voice_t *v)
{

}

int voice_lpm_data(voice_t *v, void *mic, int len)
{
    char *data = aos_zalloc(v->mic_param->period_bytes + v->ref_param->period_bytes);

    if (data == NULL) {
        return -1;
    }

    int offset = 0;
    int cpy_len = len < v->mic_param->period_bytes? len : v->mic_param->period_bytes;
    if (len < v->mic_param->period_bytes) {
        offset = v->mic_param->period_bytes - len;
        memcpy(data + offset, mic, cpy_len);
    } else {
        offset = len - v->mic_param->period_bytes;
        memcpy(data, (char *)mic + offset, cpy_len);
    }
    voice_msg_send(v, VOICE_PCM_CMD, data, len, 1);
    aos_free(data);

    return 0;
}

int voice_msg_send(voice_t *v, int cmd, void *data, int len, int sync)
{
    voice_msg_t msg;
    memset(&msg, 0, sizeof(voice_msg_t));
    int ret = -1;

    msg.command = cmd;
    msg.req_data = data;
    msg.req_len = len;
    msg.flag = sync;

    if (v->ops->msg_send)
        ret = (v->ops->msg_send(v->ch, &msg));

    return ret;
}

voice_t *voice_init(voice_evt_t cb, void *priv)
{
    voice_t *v = voice_malloc(sizeof(voice_t));
    memset(v, 0x00, sizeof(voice_t));
    // int ret;
    v->cb = cb;
    v->priv = priv;
    // ai_init();

    return v;
}

void voice_deinit(voice_t *v)
{//TODO
    voice_ch_deinit(v);
    pcm_deinit(v->pcm);
    aos_free(v);
}

int voice_config(voice_t *v, voice_param_t *p)
{
    aos_check_return_einval(v && p);

    memcpy(&v->param, p, sizeof(voice_param_t));

    if (v->param.ai_param && v->param.ai_param_len > 0) {
        v->param.ai_param = voice_malloc(v->param.ai_param_len);
        memcpy(v->param.ai_param, p->ai_param, p->ai_param_len);
    }

    return 0;
}

int voice_add_mic(voice_t *v, voice_pcm_param_t *p)
{
    if (v->mic_param == NULL) {
        v->mic_param = voice_malloc(sizeof(voice_pcm_param_t));
    }

    memcpy(v->mic_param, p, sizeof(voice_pcm_param_t));

    return 0;
}

int voice_add_ref(voice_t *v, voice_pcm_param_t *p)
{
    if (v->ref_param == NULL) {
        v->ref_param = voice_malloc(sizeof(voice_pcm_param_t));
    }

    memcpy(v->ref_param, p, sizeof(voice_pcm_param_t));

    return 0;
}

int voice_start(voice_t *v)
{
    voice_ch_init(v);
    voice_pcm_param_init(v);

    voice_cp_start(v);
    return 0;
}

int voice_stop(voice_t *v)
{
    // pcm_close(v->pcm);
    return 0;
}

int voice_backflow_control(voice_t *v, voice_backflow_id_t id, int flag)
{
    int tmep = (id) << 16 | flag;

    voice_msg_send(v, VOICE_BACKFLOW_CONTROL_CMD, &tmep, 4, 1);

    return 0;
}

int voice_wkvc_stat_send(voice_t *v, int flag)
{
    voice_msg_send(v, VOICE_WKVC_STAT_CMD, &flag, sizeof(flag), 0);
    return 0;
}

int voice_fake_wakeup_send(voice_t *v)
{
    voice_msg_send(v, VOICE_FK_WAKEUP_CMD, NULL, 0, 1);
    return 0;
}

int voice_get_kw(voice_t *v, int kwid, char **kw)
{
    aos_check_param(kw && v->kwmap);

    for (int i = 0; i < v->kwmap->count; i++) {
        if (v->kwmap->map[i].id == kwid) {
            *kw = v->kwmap->map[i].kword;
            return 0;
        }
    }

    *kw = NULL;
    return -1;
}
